#!/usr/bin/perl

use strict;

package Lunmgr;
use Exporter 'import';

our %EXPORT_TAGS = (
    all => [
        qw(
          GetMpathForUuid
          GetDevForUuid
          GetUuidForDev
          GetWwnForDev
          GetStatusForDev

          FindFCDisks

          ReadTable
          WriteTable

          LogDebug
          LogMsg
          LogWarn
          LogError
        )
    ]
);

our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );

use Time::HiRes qw/time/;

use Device;
use MSAUtils ':all';
use MSA;

use Data::Dumper;

my %_cmd = (
    multipath => '/sbin/multipath',
    scsi_id   => '/sbin/scsi_id',
    egrep     => '/bin/egrep',
    sg_inq    => '/usr/bin/sg_inq',
    lsscsi    => '/usr/bin/lsscsi',
    fdisk     => '/sbin/fdisk',
    cat       => '/bin/cat',
);

### log utilities
{
    my $_fh;

    sub InitLog {
        print "initializing log ...\n";
        $_fh = *STDOUT;
    }

    sub CloseLog {
        print "closing log ...\n";
        close $_fh if ($_fh);
    }

    sub LogDebug {
        my ($msg) = @_;
        print $_fh "Debug: $msg\n";
    }

    sub LogMsg {
        my ($msg) = @_;

        print $_fh "Info: $msg\n";
    }

    sub LogWarn {
        my ($msg) = @_;
        print $_fh "Warning: $msg\n";
    }

    sub LogError {
        my ($msg) = @_;
        print $_fh "Error: $msg\n";
    }

}

### get hardware parameters
{
    my %_uuid_mpath;    # map from uuid to mpath

    sub GetMpathForUuid {
        my $uuid = $_[0];

        return '' if ( $uuid =~ /REMOVED/ );

        if ( scalar %_uuid_mpath == 0 ) {
            chomp( my @lines = `$_cmd{multipath} -ll` );

            my $SPACES           = qr/\s+/;
            my $START_WITH_MPATH = qr/^mpath/;
            my $FIELD_COUNT      = 2;

            foreach my $line (@lines) {
                next unless ( $line =~ $START_WITH_MPATH );

                my @fields = split $SPACES, $line, $FIELD_COUNT + 1;
                $fields[1] =~ s/[()]//g;

                $_uuid_mpath{ $fields[1] } = $fields[0];
            }
        }
        $_uuid_mpath{$uuid};
    }
}
{
    my %_dev_uuid;    # map from dev to uuid

    sub GetUuidForDev {
        my ( $sddev, $force ) = @_;
        if ( $force || !$_dev_uuid{$sddev} ) {
            chomp( my $uuid = `$_cmd{scsi_id} -gus /block/$sddev` );
            $_dev_uuid{$sddev} = $uuid;
        }

        $_dev_uuid{$sddev};
    }
}
{
    my %_dev_wwn;    # map from dev to wwn

    sub GetWwnForDev {
        my ( $sddev, $force ) = @_;

        if ( $force || !$_dev_wwn{$sddev} ) {
            chomp( my $cmdout = `$_cmd{sg_inq} -p0x83 $sddev` );
            $_dev_wwn{$sddev} = $1 if ( $cmdout =~ /\[0x([0-9a-f]{16})\]/ );
        }
        $_dev_wwn{$sddev};
    }
}
{
    my %_uuid_dev;    # map from uuid to dev

    sub GetDevForUuid {
        my $uuid = $_[0];

        return '' if ( $uuid =~ /REMOVED/ );

        if ( scalar %_uuid_dev == 0 ) {
            chomp( my @lines = `$_cmd{lsscsi}` );
            foreach (@lines) {
                my @fields = split /\s+/;

                my $sddev = $fields[-1];
                $sddev =~ s/.*\///;

                my $uuid = GetUuidForDev($sddev);

                next unless ($uuid);

                if ( $_uuid_dev{$uuid} ) {
                    $_uuid_dev{$uuid} .= ",$sddev";
                }
                else {
                    $_uuid_dev{$uuid} = $sddev;
                }
            }
        }

        $_uuid_dev{$uuid};
    }
}
{
    my %_dev_pathstat;    # map from dev to path status

    sub GetStatusForDev {
        my $dev = $_[0];

        if ( %_dev_pathstat == 0 ) {
            my ( $sd, $status ) = ();
            foreach (`$_cmd{multipath} -ll`) {
                chomp;
                $sd     = $1 if (/(sd[a-z]+)/);
                $status = $1 if (/(\[\w+\]\[\w+\])/);

                if ( $sd && $status ) {
                    $_dev_pathstat{$sd} = $status;
                    ( $sd, $status ) = ();
                }
            }
        }

        $_dev_pathstat{$dev};
    }
}

### devtable
sub ReadTable {
    my ( $file, @pathes ) = @_;

    my @devices;
    open my $fh, "<", "$file" or die "Could not open $file\n";

    my $EMPTY_LINE  = qr/^\s*$/;
    my $COMMENT     = qr/^\s*#/;
    my $SPACES      = qr/\s+/;
    my $FIELD_COUNT = 5;

    while ( my $line = <$fh> ) {
        next if ( $line =~ $EMPTY_LINE );
        next if ( $line =~ $COMMENT );

        my @fields = split $SPACES, $line, $FIELD_COUNT + 1;

        my ( $wwn, $lun ) = split /:/, $fields[2];

        my $vol = join( '/', @fields[ 3, 4 ] );

        $vol =~ s/\//\\\//g;    # add backslash before '\'
        $vol =~ s/\$/\\\$/;     # add backslash before '$'

        if ( @pathes == 0 || grep { /$vol/ } @pathes ) {
            my $device = Lunmgr::Device->new(
                uuid     => $fields[0],
                model    => $fields[1],
                wwn      => $wwn,
                lun      => $lun,
                mount_pt => $fields[3],
                name     => $fields[4],
            );
            $device->set_msa( GetMsaForWwn($wwn) );
            my $mpath = GetMpathForUuid( $device->uuid );
            if ($mpath) {
                $device->set_mpath($mpath);

                my $dev     = GetDevForUuid( $device->uuid );
                my @devs    = split q{,}, $dev, 3;
                my %pstatus = map { $_=> GetStatusForDev($_) } @devs;

                $device->set_dev($dev);
                $device->set_pstatus( \%pstatus );
            }
            else {
                my $dev = GetDevForUuid( $device->uuid );
                $device->set_dev($dev);
            }

            push @devices, $device;
        }
    }

    close $fh;

    @devices;
}

sub WriteTable {
    my @devices = @_;

    print $_->ToTableStr . "\n" foreach (@devices);

}

sub FindFCDisks {
    chomp( my @lines = `$_cmd{lsscsi}` );

    print STDERR "Discovering Fiber Channel devices...   ";

    my @devices  = ();
    my $fc_count = 0;
    my @symbols  = qw(| / - \\);

    foreach (@lines) {

        my @fields = split /\s+/;

        my $lun = $fields[0];
        $lun =~ s/.*:([0-9]+).*/$1/;

        my $wwn   = GetWwnForDev( $fields[-1] );
        my $sddev = $fields[-1];
        $sddev =~ s/.*\///;

        my $uuid = GetUuidForDev($sddev);
        chomp( my $model = `$_cmd{cat} /sys/block/$sddev/device/model` );

        chmod( my $cmdout = `$_cmd{fdisk} -l $fields[-1] 2>&1` );
        my $size;
        if ( $cmdout =~ /([0-9]+\.[0-9]* GB),/ ) {
            $size = $1;
            $size .= " (unpartitioned, unformatted)"
              if ( $cmdout =~ /doesn't contain/ );
        }

        my $device = Lunmgr::Device->new(
            wwn   => $wwn,
            lun   => $lun,
            dev   => $sddev,
            uuid  => $uuid,
            mpath => GetMpathForUuid($uuid),
            model => $model,
            size  => $size,
        );

        if ( $lun == 60 ) {
            $device->set_mount_pt("/");
            $device->set_name("home");
        }
        elsif ( $lun == 61 ) {
            $device->set_mount_pt("/cm");
            $device->set_name("shared");
        }

        push @devices, $device;

        print STDERR "\x08" . $symbols[ $fc_count++ % @symbols ];
    }

    print STDERR "\x08\x08 $fc_count found\n";

    @devices;
}

sub BEGIN {
    &InitLog();
}

sub END {
    &CloseLog();
}

1;
